package cc.verywell.pureblock.msg.nfs.linkmap;

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import cc.verywell.pureblock.util.AutoClosed;

/**
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年6月19日 下午5:22:13
*/
public class EasyFileController implements Closeable {

	static public boolean IGNORE_FILE_MISS = false;
	
	static public int MAX_GRADE = 7; //分8层
	static public long MAX_TABLE_SIZE = 4*1024*1024; //每个Table文件大小，预计就行，总会比这个数大一点;
	static public float GRADE_EXPANDED = 10; //每层大小 = GRADE*EXPANDED;
	static public long MEM_SAVE_SIZE = 10*1024*1024; //内存表到达此文件后，会开始写入硬盘;
	static public long MAX_MEM_SIZE = MEM_SAVE_SIZE*2; //内存表到达此文件后，会停止写入;
	static public String FILE_HD = ".hd";
	static public String FILE_MEMLOG = ".mlg";
	static public String FILE_FILELOG = ".flg";
	static public String FILE_INTRO = "main.lg";
	
	private AtomicLong fileNumber;
	private AtomicLong seq;
	private EasyFileControllerLog filelog;
	private EasyTableGroup nowGroup;
	final private Path parentPath;
	final private AutoClosed ac;
	final private LinkCell[] lastCompacted;
	final private EasyIndex easyIndex;
	public EasyFileController(Path parentPath, AutoClosed ac, final ThreadGroup tg) {
		this.parentPath = parentPath;
		this.ac = ac;
		this.lastCompacted = new LinkCell[EasyFileController.MAX_GRADE];
		
		ByteBuffer bb = ByteBuffer.allocate(8);
		Path intro = getFileIntrofile();
		if(!Files.exists(intro)) {
			this.fileNumber = new AtomicLong(1);
			this.seq = new AtomicLong(1);
			long newFileLogNum = getNewFileNumber();
			this.filelog = new EasyFileControllerLog(getFileLogfile(newFileLogNum));
			try(SeekableByteChannel channel = Files.newByteChannel(intro, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)){
				bb.clear();
				bb.putLong(newFileLogNum);
				bb.flip();
				if(8!=channel.write(bb))
					throw new EasyIOException();
			} catch (IOException e) {
				throw new EasyIOException(e);
			}
			
			EasyTableGroup group;
			{
				EasyTablesUnsort unsort = new EasyTablesUnsort(ac, new ArrayList<>(0));
				EasyTablesSorted[] sorted = new EasyTablesSorted[EasyFileController.MAX_GRADE];
				for(int i = 0;i<EasyFileController.MAX_GRADE;i++)sorted[i] = new EasyTablesSorted(ac,new ArrayList<>(0));
				group = new EasyTableGroup(unsort, sorted);
			}
			this.nowGroup = group;
			
		}else {
			long oldfilelogNum;
			try(SeekableByteChannel channel = Files.newByteChannel(intro, StandardOpenOption.READ)){
				if(8!=channel.read(bb))
					throw new EasyIOException();
				bb.flip();
				oldfilelogNum = bb.getLong();
			} catch (IOException e) {
				throw new EasyIOException(e);
			}
			Path oldfilelog = getFileLogfile(oldfilelogNum);
			
			EasyFileControllerLog.ControllerLog nowLog = EasyFileControllerLog.readLog(oldfilelog, this);
			this.nowGroup = nowLog.group;
			this.fileNumber = new AtomicLong(nowLog.fseq);
			this.seq = new AtomicLong(nowLog.seq);
			{
				long newFileLogNum = getNewFileNumber();
				filelog = new EasyFileControllerLog(getFileLogfile(newFileLogNum));
				filelog.writetedControllerLog(nowLog, logGetFileNumber(), nowLog.seq);
				try(SeekableByteChannel channel = Files.newByteChannel(intro, StandardOpenOption.CREATE, StandardOpenOption.WRITE)){
					bb.clear();
					bb.putLong(newFileLogNum);
					bb.flip();
					if(8!=channel.write(bb))
						throw new EasyIOException();
				} catch (IOException e) {
					throw new EasyIOException(e);
				}
				try {
					Files.deleteIfExists(oldfilelog);
				} catch (IOException e) {
					throw new EasyIOException(e);
				}
			}
			
			if(nowLog.mlog!=null) {
				for (long mlog : nowLog.mlog) {
					Path mfile = getFileLogfile(mlog);
					EasyTableMemLog.TableReadLog rdLog = EasyTableMemLog.readLog(mlog, mfile);
					if(seq.get()<rdLog.bigest_seq)seq.set(rdLog.bigest_seq);
					
					EasyCompactMemToHd compact = new EasyCompactMemToHd(rdLog.table);
					compact.compact(this, memCompactDo);
				}
			}
			
		}
		this.easyIndex = new EasyIndex(this,tg);
	}
	
	@Override
	public void close() {
		this.easyIndex.close();
	}
	void closeFromIndex() {
		ac.close();
		filelog.close();
	}
	
	public final EasyIndex getIndex() {
		return this.easyIndex;
	}
	
	public EasyTableMem newTableMem() {
		long fileNumber = getNewFileNumber();
		EasyTableMem mem = new EasyTableMem(fileNumber, new EasyTableMemLog(getMemLogfile(fileNumber)));
		synchronized(this) {
			getFileLog().writetedNewMem(fileNumber, logGetFileNumber(), logGetSeq());
		}
		return mem;
	}
	
	
	
	
	final private EasyCompactFinish memCompactDo = new EasyCompactFinish() {
		public Object sync() {return EasyFileController.this;}
		public EasyTableGroup syncGetGroup() {return getTableGroup();}
		public EasyFileControllerLog syncLog() {return getFileLog();}
		public void syncSetGroup(EasyTableGroup group) {
			setTableGroup(group);
			memCompact=null;
		} 
		public void doLast() {
			Runnable run = memLastDo;
			if(run!=null) {
				memLastDo = null;
				run.run();
			}
		};
	};
	private EasyCompact memCompact = null;
	private Runnable memLastDo;
	public boolean newMemCompact(EasyTableMem mem,Runnable lastDo) {
		synchronized (this) {
			if(memCompact!=null)return false;
			memCompact = new EasyCompactMemToHd(mem);
			memLastDo = lastDo;
			return true;
		}
	}
	public boolean runMemCompact() {
		EasyCompact compact = null;
		synchronized (this) {
			if(memCompact==null)return false;
			compact = memCompact;
		}
		compact.compact(this, memCompactDo);
		return true;
	}
	
	final private EasyCompactFinish gradeCompactDo = new EasyCompactFinish() {
		public Object sync() {return EasyFileController.this;}
		public EasyTableGroup syncGetGroup() {return getTableGroup();}
		public EasyFileControllerLog syncLog() {return getFileLog();}
		public void syncSetGroup(EasyTableGroup group) {
			setTableGroup(group);
			gradeCompact=null;
		} 
		public void doLast() {};
	};
	private EasyCompact gradeCompact = null;
	public boolean runGradeCompact() {
		EasyCompact compact = null;
		synchronized (this) {
			if(gradeCompact!=null)return false;
			compact = getTableGroup().needCompact(lastCompacted);
		}
		if(compact==null)
			return false;
		compact.compact(this, gradeCompactDo);
		return true;
	}
	

	
	
	private EasyFileControllerLog getFileLog() {
		//TODO
		return filelog;
	}
	public EasyTableGroup getTableGroup() {
		return nowGroup;
	}
	//必须在 syncronized内处理;
	private void setTableGroup(EasyTableGroup newGroup) {
		this.nowGroup = newGroup;
	}
	
	
	public long getNewFileNumber() {
		return fileNumber.getAndIncrement();
	}
	long logGetFileNumber() {
		return fileNumber.get();
	}

	public long getNewSeq() {
		return seq.getAndIncrement();
	}
	long logGetSeq() {
		return seq.get();
	}
	
	public AutoClosed getAC() {
		return this.ac;
	}

	
	public Path getHDfile(long fileNumber) {
		return parentPath.resolve(String.valueOf(fileNumber)+FILE_HD);
	}
	public Path getMemLogfile(long fileNumber) {
		return parentPath.resolve(String.valueOf(fileNumber)+FILE_MEMLOG);
	}
	public Path getFileLogfile(long fileNumber) {
		return parentPath.resolve(String.valueOf(fileNumber)+FILE_FILELOG);
	}
	public Path getFileIntrofile() {
		return parentPath.resolve(FILE_INTRO);
	}
	
	
	

}
